package org.de.maeyer.sampler;

import static org.de.maeyer.util.ArrayUtils.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.collections.map.ListOrderedMap;
import org.apache.commons.lang.mutable.MutableLong;
import org.de.maeyer.statistic.Accumulator;
import org.de.maeyer.statistic.Category;
import org.de.maeyer.statistic.ObjectCategory;
import org.de.maeyer.statistic.OtherObjectCategory;
import org.de.maeyer.statistic.StatisticsBuilder;

/**
 * @author Peter De Maeyer
 */
public class ObjectHistogramAccumulator implements Accumulator {
	
	private static final int[] EMPTY_MODE_CATEGORY_INDEXES = new int[0];

	protected final Map<Object, MutableLong> categories;
	
	protected long otherCount = 0L;
	
    @SuppressWarnings("unchecked")
    public ObjectHistogramAccumulator(Object... categoryValues) {
		categories = ListOrderedMap.decorate(new HashMap<Object, MutableLong>(categoryValues.length));
		for (Object categoryValue: categoryValues) {
			categories.put(categoryValue, new MutableLong(0L));
		}
	}

	@Override
	public void reset() {
		for (Entry<Object, MutableLong> category: categories.entrySet()) {
			category.getValue().setValue(0L);
		}
		otherCount = 0L;
	}
	
	@Override
	public void accumulate() {
	    throw new UnsupportedOperationException();
	}
	
	@Override
	public void accumulateDouble(double value) {
	    accumulateObject(value);
	}
	
	@Override
	public void accumulateLong(long value) {
	    accumulateObject(value);
	}
	
	@Override
	public void accumulateObject(Object value) {
		MutableLong count = categories.get(value);
		if (count != null) {
			count.increment();
		} else {
			otherCount++;
		}
	}
	
	@Override
	public void appendTo(StatisticsBuilder builder) {
		List<Category> categories = new ArrayList<Category>(this.categories.size() + 1);
		int[] modeCategoryIndexes = EMPTY_MODE_CATEGORY_INDEXES;
		MutableLong modeCount = new MutableLong(1L); // Make sure categories with count 0 are never considered as mode category.
		int categoryIndex = -1;
		for (Entry<Object, MutableLong> category: this.categories.entrySet()) {
			long count = category.getValue().longValue();
			categories.add(new ObjectCategory(category.getKey(), count));
			modeCategoryIndexes = updateModeCategoryIndexes(modeCategoryIndexes, modeCount, count, ++categoryIndex);
		}
		if (otherCount > 0L) {
			categories.add(new OtherObjectCategory(this.categories.keySet(), otherCount));
			modeCategoryIndexes = updateModeCategoryIndexes(modeCategoryIndexes, modeCount, otherCount, ++categoryIndex);
		}
		builder.setHistogram(categories, modeCategoryIndexes);
	}
	
	private static int[] updateModeCategoryIndexes(int[] modeCategoryIndexes, MutableLong modeCount, long count, int categoryIndex) {
		if (count > modeCount.doubleValue()) {
			if (modeCategoryIndexes.length < 1 || modeCategoryIndexes.length > 1) {
				modeCategoryIndexes = new int[1];
			}
			modeCategoryIndexes[0] = categoryIndex;
			modeCount.setValue(count);
		} else if (count == modeCount.doubleValue()) {
			modeCategoryIndexes = add(modeCategoryIndexes, categoryIndex);
		}
		return modeCategoryIndexes;
	}
}